/**
 * @ Author: luoqi
 * @ Create Time: 2025-07-12 16:23
 * @ Modified by: luoqi
 * @ Modified time: 2025-07-23 00:42
 * @ Description: This file is a ymodem implementation.
 */

#include "lrbsb.h"

static uint16_t _crc16(void *data, uint16_t sz)
{
    uint16_t crc = 0;
    uint16_t i = 0;
    uint8_t *p = data;
    while(sz--) {
        crc = crc ^ ((uint16_t)(*p++) << 8);
        for(i = 0; i < 8; i++) {
            if(crc & 0x8000) {
                crc = crc << 1 ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

static int _strcmp(const void *s1, const void *s2)
{
    const char *str1 = s1;
    const char *str2 = s2;

    while(*str1 != '\0' && *str2 != '\0' && *str1 == *str2) {
        str1++;
        str2++;
    }

    if(*str1 == *str2) {
        return 0;
    } else if(*str1 < *str2) {
        return -1;
    } else {
        return 1;
    }
}

static size_t _strlen(const void *s)
{
    const char *str = s;
    if(str == NULL) {
        return 0;
    }
    const char *p = str;
    while(*p != '\0') {
        p++;
    }
    return p - str;
}

static void *_memset(void *ptr, int c, size_t n)
{
    if(ptr == NULL) {
        return NULL;
    }

    unsigned char *p = (unsigned char *)ptr;
    unsigned char uc = (unsigned char)c;

    for(size_t i = 0; i < n; i++) {
        p[i] = uc;
    }

    return ptr;
}

static int _str2num(const void *s)
{
    const char *str = s;
    if(str == NULL) {
        return 0;
    }
    int num = 0;
    while((*str != '\0') && ((*str >= '0') && (*str <= '9'))) {
        num = num * 10 + (*str - '0');
        str++;
    }
    return num;
}

int lrbsb_init(YModem *ymodem, YModemSend send, YModemRecv recv, YModemWrite write, YModemRead read, size_t timeout, size_t retry)

{
    if((ymodem == NULL) || (send == NULL) || (recv == NULL)) {
        return YMODEM_ERR_PARAM;
    }
    ymodem->send = send;
    ymodem->recv = recv;
    ymodem->write = write;
    ymodem->read = read;
    ymodem->timeout = timeout;
    ymodem->retry = retry;
    ymodem->packsz = 0;
    ymodem->filesz = 0;
    ymodem->recvsz = 0;
    ymodem->handshake_cnt = 0;
    ymodem->retry_cnt = 0;
    ymodem->packnum = 0;
    ymodem->mode = YMODEM_MODE_IDLE;
    return 0;
}

int lrb(YModem *ymodem, const char *filename, void *ptr)
{
    if((ymodem == NULL) || (filename == NULL) || (ptr == NULL) || (ymodem->write == NULL)) {
        return YMODEM_ERR_PARAM;
    }
    if(ymodem->mode != YMODEM_MODE_IDLE) {
        return YMODEM_ERR_BUSY;
    }

    ymodem->mode = YMODEM_MODE_RB;
    ymodem->state = YMODEM_STATE_HANDSHAKE;
    ymodem->last_state = ymodem->state;
    ymodem->err = YMODEM_ERR_NONE;
    ymodem->packnum = 0;
    ymodem->filesz = 0;
    ymodem->recvsz = 0;
    ymodem->retry_cnt = 0;
    ymodem->handshake_cnt = 0;

    uint8_t *waddr = ptr;
    uint8_t buf[1030] = { 0 };
    uint8_t c;
    while(1) {
        switch(ymodem->state) {
        case YMODEM_STATE_HANDSHAKE:
            c = YMODEM_C;
            if(ymodem->send(&c, 1, ymodem->timeout) != 1) {
                if(ymodem->handshake_cnt++ >= 30) {
                    ymodem->err = YMODEM_ERR_SEND;
                    ymodem->state = YMODEM_STATE_ERR;
                    break;
                }
            } else {
                ymodem->handshake_cnt = 0;
            }
            if(ymodem->recv(buf, 133, ymodem->timeout) == 133) {
                ymodem->state = YMODEM_STATE_START;
            } else {
                if(ymodem->handshake_cnt++ >= 30) {
                    ymodem->err = YMODEM_ERR_TIMEOUT;
                    ymodem->state = YMODEM_STATE_ERR;
                    break;
                }
            }
            break;
        case YMODEM_STATE_START:
            if((buf[0] == YMODEM_SOH) && (buf[1] == ymodem->packnum) && (buf[2] == (uint8_t)(~ymodem->packnum))) {
                uint16_t crc = _crc16(buf + 3, 128);
                uint16_t crc16 = (uint16_t)(buf[131] << 8) | (uint16_t)buf[132];
                if(crc != crc16) {
                    ymodem->state = YMODEM_STATE_ERR;
                    ymodem->err = YMODEM_ERR_CRC;
                    break;
                }
                if(_strcmp(filename, buf + 3) == 0) {
                    ymodem->state = YMODEM_STATE_TRANSFER;
                } else {
                    ymodem->state = YMODEM_STATE_ERR;
                    ymodem->err = YMODEM_ERR_FILENAME;
                    break;
                }
                size_t filenamesz = _strlen(filename);
                ymodem->filesz = _str2num(buf + 3 + filenamesz + 1);
            } else {
                ymodem->state = YMODEM_STATE_ERR;
                ymodem->err = YMODEM_ERR_RECV;
                break;
            }
            ymodem->packnum++;
            ymodem->retry_cnt = 0;
            ymodem->state = YMODEM_STATE_TRANSFER;
            c = YMODEM_ACK;
            ymodem->send(&c, 1, ymodem->timeout);
            c = YMODEM_C;
            ymodem->send(&c, 1, ymodem->timeout);
            break;
        case YMODEM_STATE_TRANSFER:
            if(ymodem->recv(buf, 1, ymodem->timeout) == 1) {
                if(buf[0] == YMODEM_SOH) {
                    ymodem->packsz = 133;
                    if(ymodem->recv(buf + 1, 132, ymodem->timeout) == 132) {
                        if(buf[1] != ymodem->packnum || buf[2] != (uint8_t)(~ymodem->packnum)) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_RECV;
                            break;
                        }
                        uint16_t crc = _crc16(buf + 3, 128);
                        uint16_t crc16 = (uint16_t)(buf[131] << 8) | (uint16_t)buf[132];
                        if(crc != crc16) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_CRC;
                            break;
                        }
                        if(ymodem->write(waddr, 0, buf + 3, 128) != 128) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_WRITE;
                            break;
                        }
                        waddr += 128;
                        ymodem->recvsz += 128;
                        ymodem->retry_cnt = 0;
                        ymodem->packnum++;
                        c = YMODEM_ACK;
                        ymodem->send(&c, 1, ymodem->timeout);
                    } else {
                        ymodem->state = YMODEM_STATE_ERR;
                        ymodem->err = YMODEM_ERR_RECV;
                        break;
                    }
                } else if(buf[0] == YMODEM_STX) {
                    ymodem->packsz = 1029;
                    if(ymodem->recv(buf + 1, 1028, ymodem->timeout) == 1028) {
                        if(buf[1] != ymodem->packnum || buf[2] != (uint8_t)(~ymodem->packnum)) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_RECV;
                            break;
                        }
                        uint16_t crc = _crc16(buf + 3, 1024);
                        uint16_t crc16 = (uint16_t)(buf[1027] << 8) | (uint16_t)buf[1028];
                        if(crc != crc16) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_CRC;
                            break;
                        }
                        if(ymodem->write(waddr, 0, buf + 3, 1024) != 1024) {
                            ymodem->state = YMODEM_STATE_ERR;
                            ymodem->err = YMODEM_ERR_WRITE;
                            break;
                        }
                        waddr += 1024;
                        ymodem->recvsz += 1024;
                        ymodem->retry_cnt = 0;
                        ymodem->packnum++;
                        c = YMODEM_ACK;
                        ymodem->send(&c, 1, ymodem->timeout);
                    } else {
                        ymodem->state = YMODEM_STATE_ERR;
                        ymodem->err = YMODEM_ERR_RECV;
                        break;
                    }
                } else if(buf[0] == YMODEM_EOT) {
                    c = YMODEM_NAK;
                    ymodem->send(&c, 1, ymodem->timeout);
                    if(ymodem->recv(buf, 1, ymodem->timeout) == 1 && buf[0] == YMODEM_EOT) {
                        c = YMODEM_ACK;
                        ymodem->send(&c, 1, ymodem->timeout);
                        ymodem->state = YMODEM_STATE_END;
                    } else {
                        ymodem->state = YMODEM_STATE_ERR;
                        ymodem->err = YMODEM_ERR_RECV;
                    }
                } else {
                    ymodem->state = YMODEM_STATE_ERR;
                    ymodem->err = YMODEM_ERR_RECV;
                    break;
                }
            } else {
                ymodem->state = YMODEM_STATE_ERR;
                ymodem->err = YMODEM_ERR_RECV;
                break;
            }
            break;
        case YMODEM_STATE_END:
            c = YMODEM_ACK;
            ymodem->send(&c, 1, ymodem->timeout);
            c = YMODEM_C;
            ymodem->send(&c, 1, ymodem->timeout);
            ymodem->mode = YMODEM_MODE_IDLE;
            return YMODEM_ERR_NONE;
        case YMODEM_STATE_ERR:
            c = YMODEM_NAK;
            ymodem->send(&c, 1, ymodem->timeout);
            if(ymodem->retry_cnt++ > ymodem->retry) {
                c = YMODEM_CAN;
                for(int i = 0; i < 5; i++) {
                    ymodem->send(&c, 1, ymodem->timeout);
                }
                return ymodem->err;
            }
            ymodem->state = ymodem->last_state;
            break;
        default:
            c = YMODEM_CAN;
            for(int i = 0; i < 5; i++) {
                ymodem->send(&c, 1, ymodem->timeout);
            }
            ymodem->mode = YMODEM_MODE_IDLE;
            return YMODEM_ERR_PARAM;
        }
        ymodem->last_state = ymodem->state;
        _memset(buf, 0, sizeof(buf));
    }

    return ymodem->err;
}

int lsb(YModem *ymodem, const char *filename, const void *ptr, size_t sz)
{
    if((ymodem == NULL) || (filename == NULL) || (ptr == NULL) || (ymodem->read == NULL)) {
        return YMODEM_ERR_PARAM;
    }
    if(ymodem->mode != YMODEM_MODE_IDLE) {
        return YMODEM_ERR_BUSY;
    }
    
    ymodem->mode = YMODEM_MODE_SB;


    return YMODEM_ERR_NONE;
}